Skip to content

x-correct-s 文档校正与识别

文档扫描校正存储。内置模型及高效C++算法识别和处理。校正后可选用同一套 ONNX Runtime 跑 PP-OCRv5 mobile,扫完直接出字。

兼容性

HarmonyIOSAndroidWEB小程序
支持支持支持不支持不支持

调用

ts
import { openCorrect } from "@/uni_modules/x-correct-s"

openCorrect({
  title: "文档校正",
  autoConfirm: false,
  stretch: true,
  enhanceMode: "document",
  ocr: true,
  success: (res) => {
    console.log(res.tempFilePaths)
    console.log(res.tempFiles[0].text)
    console.log(res.tempFiles[0].texts)
  }
})

autoConfirm: true 时不出现拉角和二次确认,适合连拍存档。找不到纸面四角时仍保存原图。stretch: false 时不做透视拉正:自动模式直接存原图;手动模式只显示预览图,打勾保存,不出现拉角和识别叠字。手动确认预览时右侧「完成」会隐藏,回到拍照界面再显示。

拍照落盘前先按纸面情况智能增强,且只在拉正之后做(此时纸已占满画面,统计口径才成立)。增强分两种模式,取景界面上有菜单可实时切换,默认文档增强:

  • enhanceMode: "document"(文档增强,默认):色调照搬在 Photoshop 里标定好的四个调整值 —— 亮度/对比度(PS「非旧版」)−10 / +71 → 黑白(六色权重 红19 黄50 绿21 青60 蓝27 洋红56)→ 色阶(黑场 10、中灰 1.00、白场 188,即把 [10,188] 拉满到 [0,255])→ 纸面亮度归一 → 轻锐化。PS 的亮度曲线端点固定(0→0、1→1),对比度是绕 0.5 的二次 S 曲线,所以提对比不裁高光也不切死黑;黑白用 max/mid/min 分解按色相加权,纯红/纯黄等六个原色正好等于对应权重 ×255。色调链是固定 LUT,同一张输入在任何设备上都出同一结果。链前还有两步按拍摄条件做的自适应准备(白平衡去黄偏、分块匀光去阴影和台灯亮斑,匀光带内容保护、只修纸面背景不缩放照片与墨迹),链后按实测纸面亮度做一次归一(目标 248,增益限制 0.90~1.35,纸面本身在 248 附近的图增益≈1、与 PS 逐像素一致)。另外白场会按纸面亮度自适应:纸面在色阶输入端的落点超过 188 时,白场抬到纸面之上留 12 级高光余量——拍得很白的纸因此不会整片裁成死白,纸纹和反光保留(合成亮纸图实测纸纹幅度从 2~5 级恢复到 23~33 级);纸面比白场暗时白场仍是 188,行为与 PS 一致。整条自适应准备(白平衡、匀光、自适应白场、纸面归一)可以用 -DXCORRECT_DOC_KEEP_AUTO_PREP=0 关掉,关掉后是严格 PS 复刻。锐化排在最后一步,避免字边过冲被纸面提亮顶到 255 裁平。用户给的「纯文本」与「风景票证」两张参考图都做了分位数对拍,误差在几级以内(详见 changelog)。
  • enhanceMode: "scan"(原图扫描):保留彩色,只按中位亮度温和提亮 + 轻锐化,不动白平衡和色调链,也不黑白化。适合本来就有颜色、需要留档的图。

锐化排在最后一步:它按像素亮度自适应(字边压暗限幅、亮部软上限),在最终亮度域上才能如实作用,也不会像链内锐化那样造出被纸面提亮顶裁的高光(合成图实测 71.7% 纸面像素被打满 255)。stretch: false 不拉正时画面仍含桌面背景,统计口径不成立,退回通用增强安全提亮。不另加图像库,走插件自带 C++ 核。相册图不改。

取景按 4:3 居中,上下黑边放导航、分辨率档和快门,不再铺满全屏。顶栏下列出最多 5 档拍照分辨率,默认超高清(最高一档)。三端档位的来源不同:Android / 鸿蒙直接枚举硬件支持的全部 JPEG 输出尺寸;iOS 因为「单个 format 的可选拍照尺寸通常只有 1 项」且切 format 容易闪退,改为只读枚举出传感器最大 4:3 尺寸,档位表示输出分辨率——始终按最大尺寸拍摄,落盘前按所选档位长边降采样,所以三端都能给出 5 档,切档也不会重配相机会话。

界面从上到下是:顶栏 → 分辨率档 → 相机预览 → 画质模式菜单 → 缩略图条 → 底部工具栏。画质模式菜单是居中的两个胶囊按钮「原图扫描 / 文档增强」,只在取景态显示(确认态和结果态隐藏,因为那时改动不会影响已生成的预览)。缩略图条紧贴底部工具栏。实时框只描边不填充,避免绿色盖住纸面内容。

扫描时实时框只跑 256 热力图模型,不用传统 C 算法兜底——传统算法会在纯色、手指蒙镜头、阴影边缘上凭空拼出矩形导致实时框乱跳,而模型对这些场景的响应实测 ≤0.012,真实纸面在 0.6 以上。模型检不出就不画框,拍照后的编辑确认框仍走「模型 + 传统 + 证据门禁」,兜底能力保留在静图路径上。三端都在 native 侧一趟完成「转正 + 重采样到 256×256」,不整帧转位图:Android 直接取相机分析帧的原始 RGBA 缓冲按 rotationDegrees 转正;鸿蒙实时帧是 NV21 原始像素(分析流 profile 是 CAMERA_FORMAT_YUV_420_SP),走 detect_from_nv21_live 在 C++ 里直接做 NV21→RGB + 按传感器朝向转正 + 重采样,省掉 ArkTS 侧四次图像 API 往返和中间 RGBA 缓冲的跨语言拷贝(鸿蒙前摄仍需镜像,保留原 RGBA 路径)。拍照确认框与实时框一致;找不到实时框时再对成片找角。拍照先冻结当前预览,界面不再跟着实时流走。三端接着用所选分辨率拍照口出存档图再拉正;找角和 OCR 按长边缩小。拍照失败才回退预览帧。

ocr: true 时每张落盘后用内置 PP-OCRv5 mobile 识别,结果在 tempFiles.text / texts / lines。拼文本 PDF 用无坐标的 texts,按页换行连接、页间空一行即可。iOS 取 texts 不要用模板字符串,否则会变成 Optional(文字)。手动确认(autoConfirm: false)时识别完会停在拉正图上,文字盖在对应行上,长按该行可复制。autoConfirm: true 仍是连拍,不打断预览。模型没就绪或识别被阈值丢掉时会提示原因。改完请重新编译安装。也可单独调用:

ts
import { ocrImage } from "@/uni_modules/x-correct-s"

ocrImage({
  path: imgPath,
  success: (res) => {
    console.log(res.text)
  }
})

方法

名称需要说明
openCorrect可选标题、闪光、自动确认、是否拉伸、张数、ocr打开校正弹层;点完成走 success,点关闭走 onClose
ocrImage图片路径对已有图片做 PP-OCRv5 识别,返回 text / lines

参数

字段说明默认
title顶栏标题文档校正
cameraPositionfront / backback
torch初始闪光灯false
autoConfirm自动校正并连拍false
stretch是否透视拉正(矫正拉伸)true
enhanceMode画质模式:document 文档增强(灰阶黑白)/ scan 原图扫描(保留彩色,轻提亮轻锐化)。弹层内取景界面也有菜单可实时切换document
maxCount最多张数20
outputTypejpg / pngjpg
qualityjpg 质量92
ocr校正后识别文字false

XCorrectFilepathwidthheightsourcecamera / album)、correctedtext(全文)、texts(无坐标行数组)、lines(带框)。ocrImagetext 本身就是行数组。每行带 box(四点框)、kindtext / table_cell)、fontPx建议字号,图片像素:按框内墨迹量出来的,叠字时直接用它就和图上的字一样大;检测框比字高大约 1.6 倍,拿框高算会大 20%~40%。墨迹的高度与宽度两条路都走,分数栈、上下标、括号、图形把「墨迹带」撑高时用宽度那一路,再用整页正文字号的量级兜底,所以同一页里大标题与正文的相对大小能对上),以及 inkTop / inkH / baseline(墨迹带顶、墨迹带高、基线,都是图片像素):叠字要贴着墨迹画,检测框上下留白不匀(多出的留白偏下),按框居中会让整行往下掉。画布类渲染直接用 baseline;只能按容器摆的(ArkUI Text)用 inkTop + inkH/2 当中心。没量出来时这三个都是 0,此时按框兜底。

OCR 只处理印刷/手写文本行。竖排会先转成横条再认。表格用几何方法还行列顺序(table_cell),取舍和局限见 models/README.md

更新日志

0.1.63(2026-09-12)

  • 去掉实时取景里「纸张没完全进画面:把镜头拉远一点」的提示,用户反馈它出现得太频繁、提示条正好压在底部按钮上,比它解决的事更扰人。三端都删:安卓取景框底部那颗药丸(liveHintView)、iOS 底部那行 liveHintLabel、鸿蒙弹的那句 toast。
  • 只动前台三端:C++ 的判定与 JSON 里的 hint 字段原样保留(省掉三端原生库重编),三端结果类里的 hint 字段与解析一并删掉。其余提示(「已复制」「未识别到区域,请重新拍摄」「未识别到文字」)不变。

0.1.62(2026-09-12)

  • 公式识别整条链路下线(用户决定:识别效果不稳定,收益不抵体积与故障面)。删掉的东西:
    • 原生:common/XCorrectFormula.cpp(PP-FormulaNet_plus-S 装载/预处理/解码 + 整行判定 + 行内公式 + 分数栈并块)、common/XCorrectLatex.cpp(LaTeX → 可读近似文本)、OcrBoxLine 里的 absorbed / char_x、CTC 解码回传的时间步、以及 ocr_from_rgba 里三次公式调用。三端 CMake / 构建脚本里的源文件与模型拷贝一并去掉。
    • 三端桥接:nativeInitFormula / nativeSetFormulaEnabled / nativeFormula / nativeFormulaError 四个导出(napi / JNI / ObjC)、XCorrectConfig.math 开关(含 interface.uts、三端 index.uts、演示页开关)、鸿蒙那套 294MB rawfile 的流式拷贝与字节校验代码。
    • 资源:三端打包目录里的 formula_ppformulanet_plus_s.onnx(308MB)与 formula_vocab.txtmodels/prepare_formula.shmodels/verify_formula.py、Paddle 推理件与转换 venv(约 1GB),以及 models/README.md 里的公式章节。
    • 结果结构:保留 kind(仍有 text / table_cell),删掉恒为空串的 latex —— XCorrectOcrLine(interface.uts + 三端解析 + XCorrectOcr.cpp 的 JSON 输出)、XPdfTextLine 与演示页的透传一并去掉,三端产物重新编译。
  • 体积变化:鸿蒙运行包(HAP)393MB → 83.8MB;iOS framework 581KB → 398KB、har 29.6MB → 29.6MB(OCR 模型没动)。
  • 顺带说明下线原因:最后一次真机日志 formula model missing: copy mismatch 是「拷完抽头尾比对」检查报出来的 —— 鸿蒙侧从 rawfile 流式拷到缓存的那份模型内容确实是坏的(不是大小不对,是 offset 解释不一致),ORT 装载必然失败,之前那串「装载失败 + 闪退」都源于这里。做模型校验时的经验留在 XCorrectOcr.cpp 的墨迹测量里(inkTop / inkH / baseline 继续给叠字用)。
  • 主机测试架跟着瘦身:只留整页 OCR 回归(MODE 开关与公式判定表 pred_test 一并删掉),native-src/host/README.md 重写了用法。

0.1.61(2026-09-11)

  • 叠字(预览 + PDF)改用「墨迹」落笔。OCR 一直算着墨迹带与基线(inkTop / inkH / baseline),但三端预览叠字都没用过:安卓按「框顶 + ascent」、鸿蒙按「框内居中」、iOS 从框顶再退一个 ascent —— 检测框上下留白不匀(多出的留白偏下,框常把上下标、下一行的头框进来),按框摆就会整体往下掉。现在三端预览的 OCR 行都接上这三个量:
    • 安卓:drawText 的 baseline 用 OCR 量出来的那条(没量出来才退回框顶算法);
    • iOS:从基线往上退 ascenderdraw(at:) 的点是文字框左上角)—— 顺带修掉原来从框顶退 ascent、把字整体抬高的写法;
    • 鸿蒙:ArkUI 的 Text 不能直接给基线,改成「容器中心 = 墨迹带中心」(容器高度仍是检测框高,免得 ArkUI 的行高把字裁掉),检测框本身留在原处不动。
    • 行框被缩放时(scaleOcrLines)这三个量跟着一起缩;不缩基线会带着字整体偏下。
  • 公式模型装载失败只试一次。模型 194~294MB,之前失败不记录,三端桥接每张图都会再试一遍 —— 真机上的「闪退」就是这么来的(一串几百 MB 的分配 + ORT 建图)。现在 init_formula_models 记住失败与原因,鸿蒙桥接侧也加了 formulaAttempted,失败后直接返回同一句日志,不再碰模型。
  • 鸿蒙侧补上失败原因:新增 nativeFormulaError 导出(直接返回 C++ 记下的那句话)与 hilog 输出(formula init failed: …)。nativeInitFormula 返回 false 时 ArkTS 先问它,再退回原来的「喂 8×8 空图问 errMsg」兜底 —— 那条老路任何一步抛异常都会把原因吃掉,日志里只剩「未就绪」,排查时分不清是文件坏了、版本不匹配还是没内存。
  • 正文字号不再被宽度路压小一圈suggest_font_px 里「宽度路比高度路小 15% 就切宽度路」的门槛太敏感:宽度路 = 墨迹宽 ÷ 文本宽,OCR 多认几个字就会偏小一两成,于是正常正文行被切到宽度路、画出来比图上的字小两成(真机反馈的「字小了 1/4」)。现在:
    • 高度路只有被明显撑高(墨迹带 ≥ 1.35 倍)才切宽度路 —— 撑高的真实量级是 1.6~2.3 倍(分数栈、括号、图形),两边分得开;
    • page_body_px 收样本时同样用 1.35 倍这条线,并且取高度路当中位数(宽度路会被多认的字带小);
    • 大字夹逼(> 1.15 倍正文)除了「两条路对不上」,再加一条「高度路本身超过正文 1.8 倍」—— 并块(分数栈 + 下一行)两条路会一起偏大、互相印证,只有这条量级线拦得住;真大标题 1.5~1.7 倍,不受影响。
    • 同一张数学卷(1212×1800)实测:正文 18.9~21.6 → 21.6~26.1(该页正文实测字高约 21px,换算字号约 23.9),标题仍是 35.2、选项行仍是 25.4~28.8,并块行从 58.9 回落到 28.8。

0.1.60(2026-09-11)

  • 修鸿蒙真机上「公式模型未启用:formula model missing」。模型其实好好地在包里 —— 实测打出来的 entry-default-signed.hap 里有 resources/rawfile/x-correct-s/formula_ppformulanet_plus_s.onnx(308MB,未压缩存放)。问题在读取方式:小模型(corner / OCR / 词典)走的是 resourceManager.getRawFileContentSync,而 294MB 的公式模型为了不占 300MB 堆改成了「流式拷贝」,却用 fs.openSync('x-correct-s/xxx') 当普通文件路径打开 —— rawfile 在 HAP 包里,不是沙箱里的文件,真机上必然 ENOENT,于是 releaseLargeRawFile 直接返回空串、日志只报 missing。
    • 现在改走资源管理器:getRawFdSync 拿到这段 rawfile 的 fd + 在包里的起始偏移 + 长度,按 4MB 分块 readSync(第一块用 offset 定位,之后靠文件指针自己前进)写进 cacheDir/x-correct-s/formula,结束按官方做法 closeRawFdSync 关闭 fd,再交给 C++ 侧当真实路径装载。与官方 JsEtsAPI 参考里的 readSync(data.fd, buf, { offset: data.offset, length }) + closeRawFdSync 用法一致。
    • 顺手把失败原因写清楚:formulaReleaseDetail 记录具体原因,日志里现在是 formula model missing: rawfile not packed: x-correct-s/… / rawfile too small: … 0 < 150000000 / copy truncated (n/m) / cache dir unavailable (…),下次一眼能分出「没打进包」「打不开」「拷坏了」。
    • 另外两端本来就没这个问题:Android 用 assets.open(...).copyTo(...) 流式拷(大文件 OK),iOS 用 Bundle 资源路径 + FileManager.copyItem(真实文件路径 OK)。这次只改鸿蒙 .etsx-correct-native.har 与 C++ 都没动,重新编译一次应用即可生效。

0.1.59(2026-09-11)

  • 修「叠字与纯文本出图里部分行比图上的字大一倍、画出来还被裁」。0.1.58 把字号改成按墨迹量之后正文对上了,但墨迹带比字高的行会算大一倍:二维分式栈、上下标、括号、整块图形被同一个检测框框进来时,「墨迹主体高度」量到的是整块的高度(试卷上一行分数栈的墨迹带到正文字高的 2.3 倍)。suggest_font_px 现在同时走两条路,再用整页正文字号的量级兜底:
    • 高度路(原有):墨迹主体高度 ÷ 字面比例(CJK 0.88 / 拉丁 0.96 / 大写数字 0.72)。
    • 宽度路(新增):墨迹宽度 ÷ 识别文本折算宽。新增 text_advance_em 把文本折成「几个 em 宽」(CJK 与全角标点 1.0、数字与拉丁字母约 0.5、英文窄标点 0.28),分数栈、上下标、括号、图形都影响不到它。比高度路小 15% 以上的行改用宽度路,再乘 0.85 —— 这类行的识别文本常常只是二维栈的一部分,少认几个字就把宽度路算大,两页试卷实测中位偏大 20%;另留「不低于整页正文 80%」的下限,免得正常行被相邻墨迹带高之后被压得太小。
    • 整页正文量级(新增 page_body_px):取「字数 ≥5、墨迹没有比认出来的字宽出一截」的文字行,按两条路里较小的那个取中位数。比它大 15% 以上、而两条路又对不上的行拉回 1.15 倍以内 —— 图形、被切碎的多行框、只认出几个字的长框都属于这一类;大标题因为两条路互相印证(实测相差 6%~10%)照原样保留。
  • 两页真实试卷(1206×1790 与 1210×1838,正文约 21 px)实测:
    0.1.580.1.59说明
    大标题「重庆市2025年初中学业水平暨高中招生考试」34.134.1两条路互证,不动
    「数学试题」35.235.2同上
    「参考公式:抛物线…」(含分式栈)35.224.0原来大一倍
    「4.如图,点A,B,C在…∠AOB=100°」32.322.3同上
    「9.如图,正方形ABCD…」28.419.1
    「点H,连接GH,则△DGH的面积…」30.720.3
    「10.已知整式M:a₀+a₁x+…,其中…」34.427.2被切成两段的大框
    「[2x−2<x①」(不等式碎片)44.425.6
    「14.若实数x,y同时满足…」44.327.2
    图形里认出来的「0 GB」104.218.9原来是 5 倍
    图形里认出来的「GB」59.418.9
    图案选项里的「M0」「βn」「&」68.8 / 90.3 / 62.527.2拉回正文量级
    正文「17.求不等式组:」「第一步:构造角平分线.」21.618.9 / 21.6基本不动
  • 字号不再超框之后,预览叠字与 PDF 叠字里「文字被框裁掉」的观感跟着消失(检测框本来就是字高的 1.6 倍,字号没超框就不会被裁)。三端 aar / framework / har 已按新 C++ 重新编译。

0.1.58(2026-09-11)

  • 字号改成按墨迹量出来的fontPx),不再拿框高乘系数。检测框上下有留白,实测框高约是字高的 1.6 倍(试卷正文框高 36 而字高 23,带上下标的公式行 53 vs 30),所以「框高 × 0.85」画出来比图上的字大 20%~40%,数学行最夸张。现在 XCorrectOcr.cpp 在出结果前量两件事:框内墨迹的紧边界、以及主体段高度(横向投影里墨迹总量最大的一段 —— 不能取最高的一行,分数线的横杠暗像素最多会把主体压成两行),再按字面占字号的比换算字号:CJK 0.88 em、带上升/下降部的拉丁 0.96 em、只有大写数字符号 0.72 em。实测同一页:正文 26(旧 30)、标题 34(旧 39)、公式行 35(旧 45),与图上字高一致。
  • 三端预览叠字与三端 PDF 叠字都改用 fontPx(拿不到时才退回老办法)。有实测字号时不再套 minFontPt:大扫描件上的小字本来就小,套 10pt 会把整页文字放大一倍不止,反而和底图对不上,改成只留 3pt 的绝对下限。行框被缩放时(scaleOcrLines)字号跟着一起缩。
  • x-pdf-sencodeLineObj 只序列化 text / boxkind / latex / 新增的 fontPx 全被丢掉,所以 PDF 侧一直拿不到公式标记和字号。
  • 鸿蒙侧 OCR 行没解析 kind / latex:公式行在鸿蒙上一律是 text,复制不到 LaTeX、也数不出「公式 N 条」。顺手补上 fontPx
  • 公式判定新增两道门,修「整页答案选项都被标成公式」:选项行 / 序号行(行首 A.D. 加点/顿号/括号,或带圈数字 ①②③)与可见字符 ≤ 2 的碎片不再喂模型。实测一张数学卷 78 行里原本 44 行被判成公式(\boxed{2}\frac{\text{Aa2}}{\text{Aa}} 这类脑补结果),现在降到 4 行,且都是真的数学碎片。
  • 公式判定加每页 2.5s 推理预算kClassifyBudgetMs):超预算就停止判定、剩下的行按普通文字处理。实测手机所用的 ORT 上单次推理 0.4~0.9s,进到整页链路里还会更慢(和 OCR 会话争内存/线程),不设上限时一张密集试卷能跑几十秒。
  • 公式模型默认装 foat32 版(294MB),不再默认装 194MB 的 QDQ 版。原因是手机上的 ORT(Android/iOS 1.28、鸿蒙 1.16.3)不会折叠 DequantizeLinear,每次推理都要把 194MB int8 权重算回 float32:整页链路实测 8.4s vs 5.3s,单次推理 0.88s vs 0.50s。省 100MB 体积要多花 1.7 倍时间,默认给快的,./prepare_formula.sh --qdq 可换回小体积版(脚本会打印这个取舍)。
  • PDF 叠字的底图透明度支持 0:为 0 时不画底图,页面尺寸与文字位置仍按叠图那套走(和「纯文本出图」的区别是后者会按图片比例重排页面)。
  • 演示页:底图透明度加了 0.1 档(默认,方便核对文字位置)和「0 不画底图」档;字号系数改成「字号微调」,默认 1.00 = 与图上的字一样高(原来是框高系数,默认 0.85)。
  • 行内公式(嵌在中文句子里的数学)接了一条独立路径 extract_inline_formulas:CTC 记字符时间步 → 映射回图片横向位置 → 切数学段 → 单独喂模型 → 校验通过才替换原文,行标成 mixedlatex 存这些段的 LaTeX。校验三道:不含 \begin{} / \boxed 整块结构、不是命令名当文字、LaTeX 的字母数字要覆盖原文 60% 以上(切片常切在词中间,模型会顺着编,实测把 G 读成 .\angleADG\)。
    • 它的上限由 OCR 文本决定:试卷里的行内数学恰恰是 OCR 最容易读错的部分(抛物线y=ax²+bx+c(a≠0)物线)=ax²+bx+c(a≠ )),碎掉之后切出来就是 +bx+c(a≠ ) 这种片段,模型对片段只会编,于是被校验挡掉 —— 宁可不认,也不把正文换成乱码。想让行内稳定出 LaTeX,正确做法是在图像侧检测公式区域(公式检测模型先框出行内公式),另一套模型与流程。
  • 修「模型把行内切片当二维块」的根因:预处理原来把内容贴在 384×384 画布的左上角,下面一大片黑,模型当成多行公式块、吐 \begin{aligned} 模板(13 个干净行内样本 12 个中招)。改成内容居中后同一批样本全部读对,独立公式也更准(尾巴不再错成 {}^--2nu\tau{{}}{{}0W},且不再带 \begin{array} 外壳)。这一条同时提升了整行公式与独立公式的识别质量。
  • 行内公式的规模控制:一页最多 8 次推理,每行最多 2 段,候选按「数学段长度」从大到小取(预算花在最长的公式上),并跳过首尾是运算符的碎片(+bx+c( 这种是从表达式中间切出来的)。

0.1.57(2026-09-11)

  • 修「公式行复制不到 LaTeX」:OCR 结果 JSON 里 lines[].latex 一直写的是空串,三端长按复制拿到的还是可读近似文本。XCorrectOcr.cpp 现在填的是 row.latex
  • 公式模型换成 QDQ 形式(194MB)作为默认交付,不再装 294MB 的 float32 版。int8 权重之后挂一个 DequantizeLinear(逐通道 scale)算回 float32,ORT 装载时自己折叠——用的是标准 ONNX 算子,DequantizeLinear 从 opset 10 就有,鸿蒙的 ORT 1.16.3 也认。同图实测 token 输出与 float32 版逐位一致
    • 桌面实测:QDQ 194MB / 装载 0.23s / 单张 0.12s / 峰值 745MB;float32 294MB / 0.39s / 0.10s / 827MB。少 100MB 体积、装载更快、峰值更低,代价是单张多 20ms。
    • prepare_formula.sh 默认装 QDQ 版,--fp32 可换回 float32 版(留给个别设备排查);--int8-only 废弃——它产出的裸 int8 张量 ORT 装载不了,而 QDQ 版同样是 194MB 且能装载。
    • 三端资源下限从 200MB 降到 150MB,同时容得下两版,只用来挡「资源被截断」。
  • prepare_formula.sh 第 4 步「拷贝到三端资源目录」一个都没拷:模块根目录按 models/../.. 解析到了仓库根,三个目标目录全被判为不存在,脚本却照样报成功。现在按 models/.. 解析并统计拷贝数量,一个都没找到直接报错退出。三端资源目录里装的模型名统一成 formula_ppformulanet_plus_s.onnx,桥接侧不用感知版本差异。
  • 新增 models/verify_formula.py:按 XCorrectFormula.cpp 同样的预处理喂一张图,对比各版本模型的 token 是否逐位一致,并打印解出来的 LaTeX。
  • 新增 native-src/host:主机测试。把 common/ 的 C++ 用与手机同版本的 ONNX Runtime(Android / iOS 1.28.0、鸿蒙 1.16.3)编成可执行文件,验证模型装载、C++ 预处理与 Python 参考实现是否一致、整页 OCR 链的 JSON,以及公式判定表(MODE=pred,秒级)。上面那个「整行英文被判成公式」的坑就是它抓出来的。
  • 修 aar 打包把 models/ 整个目录收进去:模型转换的中间件、paddle_formula/、Python venv 全被打进 aar,aar 从 35MB 涨到 865MB(解开 2GB)。现在先摆一个显式的暂存目录,只收模型文件,再打 aar;models/ 里的杂物再也不会进包。
  • 公式模型默认不打进 aar:aar 是入库的二进制文件,194MB 进 git 不合适,改走 utssdk/app-android/assets/(与 iOS / 鸿蒙同一条路)。真机日志若提示 formula model missing,用 PACK_FORMULA=1 ./build_android.sh 重打一次即可把模型塞进 aar。
  • prepare_formula.sh 跑完清理 334MB 的中间件 formula_fp32.onnx
  • 量化脚本不再产出中间文件 formula_ppformulanet_plus_s_int8.onnx(QDQ 版取代了它的位置)。
  • 装载失败时的提示改成「重新跑一次 prepare_formula.sh」,不再指向已废弃的 --fp32
  • 顺带把仓库 readme 里「数学公式要另加专用模型」这句旧结论改成现在的实际实现。
  • 收紧公式判定,修「整行英文正文被判成公式」。公式模型是生成式的:喂一行英文散文进去它照样吐 LaTeX(把单词塞进 \mathsf{} 再套一层 \boxed{\begin{aligned}}),只看输出里有没有反斜杠、花括号根本分不出来 —— 之前一页英文正文会被整页标成公式,预览里的文字也被替换成那串垃圾。现在两道门:
    • 判定前先挡:OCR 文本里有 ≥2 个「3 个字母以上的单词」且字母+空格占比 ≥75% → 判为散文,不喂模型(顺带省掉一次 0.1s 推理)。实测 The following identity holds...where the contour is taken... 这类整行英文全部留在 text,公式碎片(50(ν)=-−2dwπ)照旧当公式。
    • 判定后再挡:LaTeX 里出现 ≥10 个连续字母(命令名之外),或 \mathsf{ / \texted{ / \texttt{ 后面跟着 ≥3 个字母 —— 都是模型「在转写单词」的特征,判为正文。
  • 已知限制:一行公式被检测框切成碎片时(横排碎片不参与合并),每片各自识别、各自判定,预览里会出现若干小公式行。碎片本身不再被当成正文丢弃,但拼不回整条公式。

0.1.56(2026-09-11)

  • 接入公式识别(PP-FormulaNet_plus-S)XCorrectOcr.cpp 识别完所有行后,若开启 math,逐行裁出图喂公式模型,判定为公式的行 kind 置为 formulatext 换成可读近似文本、latex 存原始 LaTeX。预览与 PDF 画的是 text,长按复制的是 latex(预览端已经按这个约定处理)。
  • 新增 XCorrectFormula.cpp(模型装载 + 预处理 + 解码)与 XCorrectLatex.cpp(LaTeX → Unicode 近似文本)。三端桥接新增 nativeInitFormula / nativeSetFormulaEnabled / nativeFormula
  • 模型与词表由 models/prepare_formula.sh 一键生成,脚本里修了两个导出坑:
    • paddle2onnx 导出 while 循环时,If.3 的 then 分支算出 rank-1 [1]、else 分支是标量,ONNX 要求一致,ORT 直接抛 INVALID_ARGUMENT随机输入能跑、真实图片才崩,很容易误判成模型没问题。脚本在 Add 之后补 Squeeze 压回标量。
    • 权重以 537 个 Constant 节点内联,ORT 官方量化器只认 initializer、对它完全无效(int8 量化后文件反而更大)。脚本自己按输出通道量化:101 个张量 221MB → int8,334MB 降到 194MB,实测 token 输出与 float32 逐位一致
  • 预处理按实测确定:灰度、等比缩放到 384 后左上角补 0、归一到 [-1,1]。补边是必须的,拉伸到 384 会把公式压扁(\varrho^{-2\nu} 会错成 \varrho^{-2\mathcal P})。词表是字符级的,BPE 空格前缀(U+0120)要全部丢掉,否则会得到 \zeta _ { 0 } 这种每个字符带空格的写法。
  • 公式判定是启发式:跳过汉字占比 > 30% 的行、跳过纯数字,要求 LaTeX 里出现反斜杠命令 / 花括号 / 上下标 / 等号之一。行内嵌在文字中间的小公式不判为公式,会原样留在 text——模型训练输入是独立公式块,整行文字喂进去结果不可靠。
  • 公式模型约 294MB(自洽 float32)或 194MB(--int8-only 的量化版,ORT 加载不了),首次开启 math 时才装载,装载失败只记日志、不影响普通 OCR。两个文件都不进 git。
  • 另外把公式模块的会话单例改成有意不析构:Ort::Env / Ort::Session 若作为静态对象在进程退出时析构,会和 ORT 运行库的卸载顺序撞车(主机实测抛 mutex lock failed: Invalid argumentlibc++abi 直接 abort),移动端那是应用进程级崩溃。

0.1.55(2026-09-11)

  • 修「很白的纸处理完基本全白」。用户反馈纸略灰时效果可以,但纸本身很白时整片被做成死白。根因是纸面亮度归一的目标 248 对「纸面本来就在 240 附近」的实拍是再往上顶:顶完离 255 只剩 7 级,纸纹、反光、颗粒全被压没。现在纸面准备后 ≥ 200 时把目标降到 232(kDocPaperTargetBright),留出 23 级高光余量。
  • 匀光的高光天花板跟着一起降:亮纸档原来把纸面顶到 246,纸面归一再想压回 232,两次调整互相抵消、纸纹反而被匀光抹掉一层。现在亮纸档的匀光上限用的是同一个 232。
  • 自适应白场的余量从 12 级提到 18 级(kDocWhiteHeadroom):12 级对很白的纸同样只剩 7 级余量。
  • 合成图实测(720×960,纸面带 ±9 级低频纸纹,纸面输入 170~245 六档):
    纸面输入修前 纸纹幅度修后 纸纹幅度越界率
    17011110%
    18513170%
    20012160%
    2159150%
    2308150%
    2453130%
    即很白的纸纸纹从 3 级恢复到 13 级、中间档普遍 +4~7 级,纸面落点从 247~249 降到 232~246,六档越界都是 0%。纸面低于 200 的图完全走原逻辑,暗光档行为一字未改。
  • 新增 XCORRECT_DOC_PAPER_BRIGHT_FROM / XCORRECT_DOC_PAPER_TARGET_BRIGHT 两个 -D 口子,换参考图复标定不用改源码。

0.1.54(2026-09-11)

  • 修「预览叠字与图片上的文字大小对不上」。三端原先都把字号夹在 10–28(安卓是 10×density28×density 像素、iOS 是 10–28 点、鸿蒙是 10–28 vp),而图片是按 min(视图宽/图宽, 视图高/图高) 缩放的,任何落在区间外的行字号都和图片对不上。现改为在图片像素空间绘制:字号 = 行框高 × 0.85,再乘画布缩放落到屏幕;大标题会真的变大,小字会真的变小。三端与 PDF 叠字共用同一个 0.85 系数,预览和导出一致。
  • 新增 minFontPt(默认 10):等比换算后小于它才用它兜底,避免缩得太小看不清。只影响屏幕显示,不改坐标。
  • 叠字不再截断成长文本加省略号:框不够宽时整行横向压扁(安卓压 textScaleX、iOS 先缩字号再压字距),保证文字完整。
  • 叠字颜色从白字改成黑字,底色填充降到 25% 不透明度:白字在白纸或已被文档增强的灰阶图上几乎看不见,黑字在两种底上都清楚,也与 PDF 叠字的黑字一致。长按复制仍然保留。
  • 识别行新增 kindlatex 字段(text / formula / table_cell),长按复制公式行时给 LaTeX 原文而不是近似文本。不开 math 时全是 text
  • 新增大框切行 split_tall_boxes:高度超过中位行高 2.2 倍的横排框,在框内做横向投影,用低于峰值 16% 的槽位把框切成多个行框。试卷、表格里被 DB 检成一整块的多行靠这一步拆开,不再被识别成一行乱码。至少切出两块才替换原框,否则保留原框不切。
  • 新增多栏阅读顺序 reorder_columns:用所有行框的 x 覆盖求出「整列空白」并据此切栏,栏内自上而下排序。单栏文档找不到空白,顺序与从前完全一致,不影响原有单栏结果。
  • 检测长边从 960 提到 1280,密集小字(试卷、表格里的小字号正文)检出率明显提高。
  • 新增有线表格还原 order_wire_table:纯几何,不用模型。按像素投影找贯穿的横竖框线,把行框按「行 × 列」重新排序,并把落在表内的行标成 table_cell,让表格内容按自然阅读顺序输出。
  • 公式模型实测后暂不内置,原因写在 models/README.md:最小的 PP-FormulaNet-S 权重就有 221MB,超出体积预算;HuggingFace 只提供 Paddle 格式,要自己调社区导出链路才能出 ONNX;鸿蒙侧 ORT 1.16.3 只认 ONNX IR 8,导出工具链默认产出的高 IR 需要降级。math 选项与 kind / latex 字段已在接口上保留,模型就位即可接。
  • 三端 aar / framework / har 已按新 C++ 重新编译,XCorrectTable.cpp 已进三端构建脚本。

0.1.53(2026-09-11)

  • 修「拍得很白的纸,处理后整片裁成死白」。用户反馈:纸略灰的实拍效果可以,但纸本身很白时处理后基本全白。原因是 PS 的色阶白场 188 是绝对值——纸面在色阶输入端的落点一超过 188,整片纸连同纸纹、反光一起被裁成 255,链后的纸面亮度归一(增益 <1)只能把 255 压到 248,压回来的是一整块平色,纹理已经丢了。
  • 修法是自适应白场:纸面在色阶输入端的落点(ps_bc_map(paperLevel))一旦超过用户白场,就把白场抬到「落点 + XCORRECT_DOC_WHITE_HEADROOM(默认 12 级,约 6% 余量)」,纸面因此落在白场之下,纸纹按近乎 1:1 保留:
    • 纸面落点 205 → 白场 217 → 纸面输出 (205−10)/(217−10)×255 ≈ 238;
    • 纸面比白场暗时白场仍是 188,后面照常走纸面亮度归一(把偏暗的纸抬到 248),原有行为一字未改。
    • 走自适应白场时纸面归一不再往上提(增益封顶 1.0):抬白场留出的余量就是用来保纸纹的,再提一次会把余量吃掉。
  • 合成亮纸图实测(纸面带 ±6 级低频纸纹,横条文字,纸面 170~235 五档):
    纸面处理前 纸面均值/纸纹幅度修前 处理后修后 处理后
    170169.5 / 11244.8 / 30244.8 / 30(不触发,与修前一致)
    185184.5 / 11246.3 / 24246.3 / 24(同上)
    200199.5 / 11249.4 / 5242.6 / 33
    215214.5 / 11249.4 / 2243.6 / 26
    235234.5 / 11249.4 / 2244.5 / 23
    即白纸档的纸纹幅度从 2~5 级(肉眼就是死白)恢复到 23~33 级,纸面落点 243~246 仍在参考图的 239~250 区间内;五档越界(≥254)都是 0%。
  • 两张参考图复核(确认没退化,纸面落点反而更贴参考):湿巾 p25/p50/p90/均值 从 234/250/250/222.3 变成 221/243/250/216.8(参考 223/239/248/219.8);门票整页 71/167/220/248/149.6 变成 67/159/209/242/144.2(参考 89/164/206/251/153),票面白色抬头区开始出现纯白(4.3%,参考 7.1%);蓝色票根区均值 153.7 → 146.0(参考 154)。
  • 新常量 XCORRECT_DOC_WHITE_HEADROOM 留了 -D 口子:余量给小了白纸还是会被裁一片,给大了纸面会停在浅灰。三端 aar / framework / har 已按新 C++ 重新编译。

0.1.52(2026-09-11)

  • 文档增强的色调链换成按 Photoshop 标定的固定值。用户按 PS 调好的四个调整原样搬进 C++:亮度 −10、对比度 +71(PS「亮度/对比度」非旧版,范围 −150..150 / −50..100)→ 黑白六色权重(红 19、黄 50、绿 21、青 60、蓝 27、洋红 56)→ 色阶(输入黑场 10、中灰 1.00、输入白场 188,即 [10,188] 拉满到 [0,255])。原来的「双峰锚点色调曲线 + Rec.601 去色」整段删除,XCORRECT_DOC_PAPER_K / XCORRECT_DOC_INK_K 与不再有人调用的 desaturate_rgba 一并移除。用户提供的是「纯文本 + 有风景照片/彩色票根」两类场景的折中值,两条参考图(原图 + PS 成图)都做了逐项比对,见文末。
  • 三处算法要点,都不是「看起来像」的近似:
    • 亮度/对比度是 PS 非旧版算法,不是旧版的「线性偏移 + 会裁两端的斜率」。亮度端点固定(0→0、1→1),正向是「低段增益射线 σ=2^(b/110)、到 y=0.5 后一段三次 Hermite 收到 (1,1)、端点斜率 τ 比中段小」,负向是正向曲线的严格反函数(PS 就是这么算的,不是镜像),用固定 64 步二分求逆;对比度是绕 0.5 的二次 S 曲线 β = 1 − 0.0076c,端点同样固定。所以 +71 提对比不会把纸面顶爆、也不会把墨迹切死(c=100 时中段斜率也只有约 1.76)。
    • 黑白用 max/mid/min 三段分解:灰底 = min,复色段 (mid−min) 按 min 是哪个通道吃青/洋红/黄,单色段 (max−mid) 按 max 吃红/绿/蓝。中性色完全不受权重影响;纯红/黄/绿/青/蓝/洋红正好等于对应权重 ×255,与 PS 行为一致(这部分与 PS CS 的公开公式互证)。
    • 锐化排在最后(纸面亮度归一之后)。放链内会先造出比纸面更亮的字边过冲,再被纸面归一整体提亮顶到 255 裁平——合成图实测 71.7% 的纸面像素被打满;放最后则锐化自身的压暗限幅与亮部软上限在最终亮度域上如实生效,纸面越界归零。
  • 保留 document_auto_prepare_rgba(白平衡去黄偏 + 分块匀光)作为拍摄条件修正,另加一道纸面亮度归一 paper_target_rgba(目标 XCORRECT_DOC_PAPER_TARGET = 248、增益限制在 0.90~1.35)。这一版顺手修了匀光的两处毛病:
    • 参考基准从「块背景中位数」改成「块背景的 0.85 高分位」。票证这类「照片 + 大色块 + 白纸」的页面里照片块可能占一半以上,中位数会被照片拖低(实测门票页中位数 130,只有纸面 185 的七成),匀光于是把纸面和大色块一起往照片的亮度上压(实测蓝色票根区 −8 级),再被色阶的 1.43 倍斜率放大成 −17 级。
    • 加内容保护:匀光增益按像素亮度加权(≥0.72×纸面基准全量、≤0.42×完全不施加),只修纸面/背景的照度不均,不再把照片、大面积色块、墨迹当背景一起缩放。
    • 匀光强度解耦成 XCORRECT_DOC_FLATTEN(默认 0.50,原先是硬编码 0.88)。强度下调是因为「浅色大色块」和「被阴影压暗的纸面」在算法上无从区分:0.88 时票根区被抬高 15%(+19 级),0.50 时约 8 级,纸面匀光仍能修掉一半以上的明暗差。
    • 纸面归一的增益上限卡在 1.35 而不是 2.0:目标 248 离 255 只剩 2.8% 余量,提得越猛越容易把纸面亮部裁平(1.94 倍时合成图 71.7% 的纸面像素被打满 255)。代价是非常暗的图(纸面 ≲130)会停在浅灰而不是纯白——「宁可不够白也不裁平」,与用户此前反复要求的「别丢纸纹和反光」一致。
  • 想严格复刻 PS(关掉白平衡、匀光、纸面归一)编译期加 -DXCORRECT_DOC_KEEP_AUTO_PREP=0 即可;所有 PS 值(XCORRECT_PS_BRIGHTNESS / CONTRAST / BW_* / LEVELS_*)都留了 -D 覆盖口子,换参考图复标定不用改源码。色调链另有独立入口 ps_tone_chain_rgba,主机端对拍不必绕 warp。
  • 主机端探针(#include 直接抽取 XCorrectCore.cpp 编译,避免手抄漂移)实测:
    • 六色权重映射:纯红 255→48(19%)、纯黄→128(50%)、纯绿→54(21%)、纯青→153(60%)、纯蓝→69(27%)、洋红→143(56%);中性 0/64/128/200/255 全部恒等。
    • 亮度/对比度曲线:0→0、16→8、64→43、128→116、160→160、192→196、224→228、255→255,端点固定、单调;色阶:10→0、11→1、20→14、100→129、187→254、188→255;整条链(灰阶)32→11、128→152、160→215、188→255,全表单调不减。
    • 合成文档图逐点核对(暖光纸面 RGB 比 1 : 0.972 : 0.822,横条文字;另测一档手影 0.74→1.0):纸面 120/140/160/180/200 档最终落在 187 / 239 / 250 / 250 / 250,纸面越界(≥254)0%;带手影档落在 133 / 175 / 214 / 221 / 233,越界同样 0%。
  • 与用户两张参考图的对拍(参考图是同内容 PS 成图的截图,口径统一为:原图取纸面内区,跑我们完整文档增强,再比区域分位数):
    • 纯文本(湿巾包装):参考 p25 223 / p50 239 / p90 248 / 均值 219.8 / 纯白 0.2%;我们 234 / 250 / 250 / 222.3 / 0%
    • 风景票证整页:参考 p25 89 / p50 164 / p75 206 / p90 251 / 均值 153 / 纯白 7.1%;我们 71 / 167 / 220 / 248 / 149.6 / 0%(参考图那 7.1% 纯白来自票面白色抬头区,我们归一后停在 248,差 7 级)。
    • 同一张票证的蓝色票根区(大块中间调,最能看出匀光是否把内容当背景拉平):参考 p25 155 / p50 164 / p75 168 / 均值 154;我们 142 / 171 / 183 / 153.7
    • 把两张参考图反查回输入(用链的逆推)得到「用户 PS 输入」的分位数,与我们手上原图裁剪区相差不到 10 级(参考图是微信转发件 + PS 截图,输入本身已被压缩/重采样过),所以上面几级的差异主要来自输入而不是链路。
  • 算法出处与验证状态:PS 非旧版亮度/对比度取自公开的逆向标定结果(15bit 全档扫描,450 张 15bit 捕获全部落在 1 LSB15 内),黑白公式与另一份 PS CS 的公开公式互证。本机 PS 25.11 的脚本对拍没跑通do javascript 在本机不执行,脚本未落地任何文件),所以没有逐像素对拍数据;要复验可以拿一张灰阶 ramp 图在 PS 里跑一遍同样的四个调整,再喂给 ps_tone_chain_rgba 比表。
  • 三端 UI 注释(Android / iOS)同步改成「先白平衡 + 匀光,再按 PS 标定色调链出灰阶黑白」。

0.1.51(2026-09-11)

  • 修安卓「原图扫描 / 文档增强」两个胶囊按钮紧贴无间距:rebuildModeChips 把 chip 直接 addViewmodeStrip,既没给 chip 设 margin,modeStrip 也没设 divider/showDividers,两个按钮就贴在一起。iOS 靠布局算 gap: 12、鸿蒙靠 Row({ space: 12 }),所以只有安卓缺间距。修法是给非最后一个 chip 设 marginEnd = dp(12),与另外两端对齐。只给非最后一个加:modeStripWRAP_CONTENT 且居中,末尾也加会让整组偏左半个间距。
  • 对照检查了同一屏的分辨率档 qualityChip,它用 LinearLayout.LayoutParams(0, WRAP, 1f) 按权重平分整行宽度,等分格子之间天然不需要间距,与胶囊按钮是两种布局模式,不受本次改动影响。

0.1.50(2026-09-11)

  • 修鸿蒙「拍照瞬间图片向左转 90°、等确认框出来才回正」:这是 0.1.49 为优化卡顿把分析帧缩到 640×480 后,连带把 freezeStillPath 的冻结预览改成优先走 createPixelMapFromSurfaceSync(绑全分辨率预览流)时引入的回归。surface 抓出来的是传感器方向的原始像素,而那条新路径调的 savePixelMapJpeg 只负责编码、完全不转正;原来的 NV21 路径走 saveNv21Jpeg,内部会按 sensorOrientationrotateSyncsensorOrientation 默认 90,于是冻结预览横躺,等 captureHdPath 拿到带 EXIF 转正的高清照片覆盖后才回正。修法是把转正搬到 surface 抓拍路径上:按 sensorOrientation 归一到 0/90/180/270 后 rotateSync,前摄再 flipSync 镜像,与 saveNv21Jpeg 完全同一套约定(该约定改动前一直在用,方向可靠)。
  • rotateSync 未必对 surface 抓拍得到的 PixelMap 可用(saveNv21Jpeg 是显式传 editable: true 创建的 PixelMap 才旋转的,SDK 文档对 rotateSync 只标注 401/501 未承诺可编辑性),所以整段包 try:一旦旋转或落盘失败就退回 lastFrame 路径,那条走 saveNv21Jpeg,转正已被验证可靠,最坏只是冻结画面偏糊(640×480),绝不会方向错。同时把 surfaceMap.release() 移进 finally——原先它在 rotateSync 之后,一旦旋转抛异常这张全分辨率 PixelMap 就永不释放,每次拍照泄漏一次。

0.1.49(2026-09-10)

  • 新增画质模式菜单:相机预览与缩略图条之间加「原图扫描 / 文档增强」两态切换,三端样式统一(选中绿底黑字胶囊、未选中透明底白字细描边),默认文档增强。原图扫描 保留彩色只做轻提亮 + 轻锐化;文档增强 走去黄偏 → 分块匀光 → 双峰锚点色调曲线 → 锐化 → 按 Rec.601 亮度转灰阶黑白(不做二值化,保留层次)。模式只在取景态可切,确认/结果态隐藏,避免给出一个不会改变当前预览的开关。C++ 侧新增 EnhanceMode 枚举与 enhance_by_modenativeWarpenhance 参数改为 mode,另加 nativeEnhanceMode 供不拉正路径使用。
  • 修实时框中间的绿色填充:三端都改成只描边不填充(Android 去掉 fillPaintdrawPath、iOS 只 stroke()、鸿蒙 Polyline fillOpacity(0))。填充会盖住预览内容,看不清纸面实际位置。
  • 缩略图条下移贴近底部拍照工具栏:Android 下内边距 12→4dp、上 12→8dp;鸿蒙 List 高 88→74、上 12→6、下 12→4。模式菜单插在预览与缩略图之间,两者间距由菜单自然撑开。
  • 修「增强后对比度偏强、字迹发死黑」。分阶段探针(用原图墨迹掩码固定采样同一批像素)测出真正的元凶不是色调曲线而是锐化:曲线对对比度只贡献 +2.5,锐化贡献 +38.7——字迹像素被亮纸面包围,模糊值远高于字心,非锐化掩模的 diff 是大负值(实测墨迹 46 → 模糊 93 → add≈-28),把墨迹从 46 直接砸到 18,死黑率从 0% 涨到 1.40%。修法是给锐化加压暗限幅(darkenLimit=6)和提亮天花板(kWhiteCeiling=250),只限两端、不伤中间过渡带。同时把锐化强度从 kDocStrength 解耦成独立常量 kDocSharpen(原先两者共用一个系数,降对比度会连带降清晰度,与需求相反)。纸面亮度 160 档的标定结果,分两组口径:分阶段探针(固定掩码)测得对比度增量 +41.3 → +28.3、锐化对墨迹的压暗 27.7 级 → 9.9 级、死黑率 1.40% → 0%;全图清晰度指标测得边缘梯度 6.36 → 6.96、拉普拉斯方差 3670 → 3868、过曝 0.11% → 0%。即对比度降下来、清晰度和过曝表现同时变好。曲线侧另把 targetPaper 0.38→0.26、targetInk 0.22→0.15。四个常量都留了 -D 覆盖口子便于复标定。
  • 修安卓裁剪后清晰度不如预期:相机图在 processFile 里已被 saveToFile(q=95) 覆盖写回工作文件,而 confirmReview 又从该文件按 4096 重新解码——preview 与工作文件几何完全一致,这次重解码拿不到任何额外像素,只多付一代 JPEG 压缩损失。改为相机源直接用 preview,相册源仍重解码(它在 processFile 只解到 1600,重解码到 4096 才有真实收益)。
  • 修鸿蒙实时框卡顿。根因是每帧要在 UI 主线程上跑四次 ArkTS 图像 API:createPixelMapSync(NV21→RGBA 全帧)→ scaleSyncrotateSyncreadPixelsToBufferSync,再把 RGBA 缓冲跨语言传给 native;ArkTS 的 enqueue 只是 Promise 链,并不切线程,ORT 推理期间整个 UI 线程被冻结(主机实测 256 模型推理 68.5ms,手机更久)。现新增 native 入口 detect_from_nv21_live / nativeDetectLiveNv21,在 C++ 里一趟完成 NV21→RGB(BT.601)+ 按传感器朝向转正 + 双线性重采样到 256×256,四次 ArkTS API 往返和中间 RGBA 缓冲的跨语言拷贝全部省掉。前摄仍需镜像(模型预处理不做镜像),故前摄保留原 RGBA 路径。另把分析流 profile 从 1920×1440(2.7MP)降到 640×480,单帧像素量降到 1/9;预览流不受影响,仍是 1920×1440。因分析帧变小,freezeStillPath 的冻结预览改为优先走 surface 抓拍(绑的是全分辨率预览流),lastFrame 退为兜底,避免冻结画面变糊。
  • NV21→RGB 转换用 Python 参考实现对拍验证:把 preprocess_bgr_nchw_nv21 函数体直接从源码抽取编译(避免手抄漂移),在 0/90/180/270 四个旋转角上与参考实现逐位比较,max|diff| = 0.000000,含行尾 stride 填充的场景一致。
  • 顺带修正上一轮的错误结论:曾推断「鸿蒙 ORT 1.16.3 跑不了 opset 16 的找角模型」。实测 ORT 1.16.3 能正常加载并推理,真实纸面 min-peak 0.636~0.873、负样本 ≤0.033,与 ORT 1.29 一致;libonnxruntime.so 的 strings 里也有完整的 8 条 ellipsis 错误消息。鸿蒙实时框不出的真实原因是帧格式判错:分析流 profile 是 CAMERA_FORMAT_YUV_420_SP,交付的是 NV21 原始像素,而 decodeBufferToRgba 用的 createImageSource 只认 JPEG/PNG 编码流,喂 NV21 必然抛异常返回 null,于是每帧 liveMiss++,框永远出不来;拍照路径正常是因为它走 photoOutput.photoAvailable 拿真实照片文件,冻结预览正常是因为 freezeStillPath 本来就有 isJpegBuffer + NV21 分支,只有实时检测漏了这一支。
  • 三端 aar / framework / har 均已按新 C++ 重新编译,nativeEnhanceModenativeDetectLiveNv21 等新符号已核对导出,Android JNI 导出与 Kotlin external 声明、Harmony NAPI 注册与 .d.ts 三处逐一对齐。

0.1.48(2026-09-10)

  • 撤销 0.1.47 给实时路径加的「传统 C 算法兜底」,实时框改为只走找角模型。0.1.47 为了修鸿蒙引入了三档分流,其中「模型还没成功检出过就先跑模型、没结果再补跑传统」这一档直接导致安卓/iOS 识别率暴跌:开机对准文档之前的头几帧必然检不出纸,传统算法就在小框、阴影边缘上凭空拼出矩形,note_fallback(true) 连续累加 3 次后把模型永久判为「不可靠」,此后整个会话都只跑传统算法,于是实时框乱跳、大块纯色和手指蒙镜头也能出框。现在实时路径彻底不再调用传统算法:模型检出就画框,检不出就不画,纯色/手指/阴影一律 found=false。拍照后的编辑确认框仍走 detect_quad(模型 + 传统 + 证据门禁),兜底能力保留在静图路径上,功能没有缺失。
  • 用真实模型实测标定后把采纳门槛 kMinScore 从 0.30 提到 0.40。跨 ORT 1.16.3 与 1.29、跨全部合成场景可复现的硬事实是负样本上界:纯黑 0.0012 / 纯白 0.0017 / 纯灰 0.0013 / 纯红 0.0011 / 手指蒙镜头 0.0015 / 阴影边缘 0.0011 / 木纹桌面 0.0017 / 随机噪声 0.0067,最高 0.0121,0.40 门槛是其 33~59 倍余量。没有取更高的 0.50+,是为真实暗光、倾斜、低对比纸面保留召回:合成纸面的分数会随矩形边界差 0.04 从 0.002 跳到 0.744,落在模型决策边界上,不能拿它当调参依据,所以只按可复现的负样本上界抬高,不做过度拟合。
  • 修 0.1.46 给模型结果叠加证据门禁造成的漏检:detect_quad_ex 里模型检出后还要求 modelScore >= 0.72 或像素级证据通过,等于把 0.40~0.72 分的合法低对比纸面又推回传统算法。实测模型对纸桌亮度差仅 2 级的极端低对比文档也给 0.72~0.91 分,说明 kMinScore 这一道关已经足够,模型结果直接采信。证据门禁保留给传统算法路径,它本来就是为「传统算法凭空拼框」设计的,实测禁用后渐变桌面会全部误检。
  • 修鸿蒙实时框不出的真正原因:帧格式判错,与模型、ORT 版本都无关。鸿蒙实时帧来自绑成 PreviewOutputImageReceiver,profile 申请的是 CAMERA_FORMAT_YUV_420_SP,交付的是 NV21 原始像素,而 decodeBufferToRgbaimage.createImageSource(buffer) 解码——它只认 JPEG/PNG 编码流,喂 NV21 必然抛异常返回 null,于是每帧 liveMiss++,框永远画不出来。拍照路径正常是因为它走 photoOutput.photoAvailable 拿真实照片文件;冻结预览路径也正常,因为 freezeStillPath 里本来就有 isJpegBuffer 判别 + stripNv21Stride + saveNv21Jpeg 的 NV21 分支,只有实时检测路径漏了这一支。现新增 decodeNv21ToRgbacreatePixelMapSync + srcPixelFormat=NV21scaleSync 降到 512 → 按 sensorOrientation 转正 → 前摄镜像 → readPixelsToBufferSync),实时路径按 isJpegBuffer 分流:真 JPEG 走原解码,NV21 走新解码。
  • 顺带修正上一轮对鸿蒙的错误结论:曾推断「鸿蒙 ORT 1.16.3 跑不了 opset 16 的找角模型(含 20 个带 ellipsis 的 Einsum)」。实测 ORT 1.16.3 能正常加载并推理该模型,真实纸面 min-peak 0.636~0.873、负样本 ≤0.033,与 ORT 1.29 结果一致;libonnxruntime.so 的 strings 里也有完整的 8 条 ellipsis 错误消息。据此删除了为这个错误推断而写的 corner_model_proven / corner_model_note_fallback / lose-streak 降级 / copy_rgba_rot / kLiveWorkEdge / try_model 等全部死代码。
  • 硬故障短路改到 detect_by_modeldetect_by_model_buf 内部:连续 3 次 Run 抛异常或输出形状非法即停用模型,不再每帧白付一次推理;热力图峰值低属正常「没对准纸」,不计入硬故障。

0.1.47(2026-09-10)

  • 修三端「模型加载失败后每帧重试」拖垮实时帧率:ensureCornerModel 原先只判断 if (modelReady) return,加载失败后 modelReady 恒为 false,于是每一帧都会重新走一遍「从 rawfile/assets/Bundle 释放 4.7MB 模型 → nativeInitModel 重新加载」。iOS 更严重:guard let caches ... else { return } 在置位之前就返回,连失败状态都没记住。在 150ms 节流下每帧白付几百毫秒,实时框自然出不来。现三端都加 modelAttempted 标志(iOS 置于所有提前 return 之前),一次会话只尝试一次,失败就靠传统兜底跑完整个会话,重进页面/重启 App 才再试。
  • 修鸿蒙实时框不出:0.1.46 把三端实时路径统一成「只跑 256 找角模型、不再跑传统管线」,但没留兜底。鸿蒙的 ORT 是运行时 dlopen 的 1.16.3(安卓/iOS 是 1.28.0),找角模型一旦不可用,实时路径就永远返回 false,框再也画不出来;而拍照后的编辑框走 detectRgba(模型失败仍有传统算法兜底)所以正常,表现为「实时看不到框、拍照后却有编辑框」。现按三档分流,兼顾「能出框」与「够流畅」:
    • 模型已证明可用(本次会话成功检出过一次):每帧只跑 256 热力图,本帧没纸就返回不画框,绝不补跑传统检测。这是安卓/iOS 的快路径,流畅度与 0.1.46 一致。
    • 模型不可用(加载失败 / 连续硬故障):直接走传统检测,一次模型推理都不白付。
    • 模型能加载却还没成功检出过:先跑模型,没结果就补跑传统。这一档专门兜住「加载成功、Run 不报错、却始终算不出纸面」的隐蔽故障——上一版「纯色画面也能出实时框」正说明鸿蒙走的就是传统路径,属于这一档。 回退时工作图长边从静图的 800 降到 400(实测 720p 帧约 27ms),且证据门禁照常生效,纯色/无纸仍不会误检。ORT 层用「模型输给传统」的次数做安全降级信号:连续 3 帧「模型没检出而传统检出」即认定模型在本平台不可靠,此后锁定纯传统路径,不再每帧白付推理;对着白墙时传统同样检不出,计数不增长,故不会误降级。实测该档模型只在前 3 帧被调用,之后归零。
  • 修实时兜底每帧重复推理:detect_quad_ex 内部本来就会先试一次模型,实时兜底路径外层已经试过,等于每帧白付两次推理。给它加 try_model 参数,兜底调用传 false 跳过内部尝试,每帧模型调用从 2 次降到 1 次(降级后为 0 次)。
  • 修增强后图片偏黑:0.1.46 的色调曲线 gamma = 1 + 0.30 × strength(strength 60 时约 1.18)方向是反的——gamma>1 的幂函数把中间调整体压向墨迹一侧,实测字迹与纸面之间的浅灰平均压暗 4.6 级、高光 235 级被压到 224.6,肉眼就是整页发灰发黑。文档增强的正确方向是把纸面提干净:gamma 改为 1 - 0.14 × strength(<1,中间调向纸白提亮),高光斜率 0.50→0.70(少压高光、保住纸纹),墨黑压缩 0.42→0.30(字迹靠锚点压深即可,不必再靠曲线),默认强度 60→50。实测同场景 mean 143.9→157.3、纸面均亮 160.7→179.6(原先是 143.9→109.0 一路压暗),动态范围仍变宽、黄偏仍消除约 8 成、零过曝。
  • 修 iOS 只有 1 档分辨率:原先 collectPhotoSizes 只读当前 activeFormat.supportedMaxPhotoDimensions,而该属性是「单个 format」的——iOS 上一个 4:3 format 通常只给 1 项(另一项是 16:9,被 4:3 过滤掉),所以只剩一档;安卓/鸿蒙枚举的是硬件全部 JPEG 输出尺寸,故有 5 档。跨 format 枚举要切 activeFormat,而 0.1.43/0.1.44 记录过切 format 未 lock 直接闪退。改为:只读遍历 device.formats 取传感器最大 4:3 尺寸(不切 format,零闪退风险),档位表达「输出分辨率」——始终按传感器最大尺寸拍摄,落盘前按所选档位长边降采样(快门时快照档位,避免工作线程读主线程状态)。长边按 32 对齐、封顶到 warpMaxEdge(否则 48MP 设备会标 8064 而实际只出 4096,标签虚高)。实测 12MP/48MP/iPad 8MP/5MP 各传感器均给出 5 档,切档不再重配会话。
  • 鸿蒙实时帧解码长边 720→512:模型只要 256×256,回退路径内部也按 400 工作,720 让每帧多做一倍以上的 JPEG 解码、rotateSync 与 readPixels。

0.1.46(2026-09-10)

  • 修纯色误检:黑色/任意纯色画面不再凭空识别出纸张边框。scan_edge 原先 bestScore 初值 -1,纯色图每条扫描线梯度为 0,0 > -1 成立导致返回 margin 位置拼出内收矩形,而几何打分对完美轴对齐矩形几乎满分,在零边缘证据下也过 1.35 门槛。新增证据门禁 measure_evidence/evidence_ok:量四条边「框外亮度 - 框内亮度」带状均值阶跃与边梯度,取中位数并统计达标边数,至少 2 条边达标才认定找到纸;用带状均值而非逐点绝对差,随机噪声的内外差会相互抵消,只有连贯边界才有稳定阶跃。找角模型本身对纯色已正确拒绝(热力图峰值约 0.003,远低于 0.30 阈值)。
  • 实时框提速:三端原先不一致——iOS 走 256 模型,Android/Harmony 却每帧跑完整 800px 传统管线(本机实测 47–144ms)。现统一只跑 256 热力图模型。Android 相机分析帧不再 toBitmap → rotateBitmap → scaleToMaxEdge → bitmapToRgba(四次 Bitmap 分配 + 69 万像素 Kotlin 逐像素循环),改为直接取 RGBA 分析缓冲原始字节 + rowStride,native 在重采样到 256×256 时顺带按 rotationDegrees 转正,把每帧遍历压到 6.5 万像素。节流 Android 220→150ms、Harmony 240→150ms,实时平滑系数三端统一 0.42/0.66→0.5。
  • 裁剪后画质智能化:新增纸面增强 enhance_document_rgba,在 warp 之后调用(此时纸已占满画面,统计口径才成立)。文档亮度是双峰分布(墨迹峰 + 纸面峰),不能用分位数当中灰——中位数落在纸峰上、用它定 gamma 会把字迹整片压黑;改为分别找两个峰当锚点重建曲线。流程:白平衡(把纸面峰附近像素拉中性,并做亮度归一,只改色度不改亮度,避免去黄偏时把纸压暗)→ 分块光照均匀化(按每块背景 0.85 分位归一,去阴影和台灯亮斑,带高光软膝防死白)→ 双峰锚点连续色调曲线(墨迹内部线性保留层次、高光半斜率压缩保住纸纹)→ 亮度非锐化掩模轻锐化(等量加回三通道不引入彩边)。
  • 修原增强时机:拉正路径原先在 warp 之前对未拉正的整帧做 auto_enhance(统计混入桌面背景)再 saveToFile 覆盖、warp 又编码一次,双重 JPEG。现拉正路径的增强移入 warp 内部(单次编码),auto_enhance 只保留给 stretch: false 的非拉正路径(画面仍含背景,用通用增强安全提亮)。
  • 新增 native 入口:nativeDetectLive(带 stride + 顺时针转正角,直接从相机缓冲找纸)、warp_from_rgbaenhance 强度参数;detect_from_rgba 支持 stride 并在返回 JSON 里附 evidence(step/energy/sides)。三端 aar / framework / har 已重新编译。

0.1.45(2026-09-09)

  • iOS 实时框改为相机 BGRA 直接下采 + 只跑找角模型,不再每帧 CIContext 出图;图标字体按文件真实名运行时注册,避免全是问号。

0.1.44(2026-09-09)

  • iOS 分辨率档不再切换 activeFormat,只在当前预览 format 上设 maxPhotoDimensions,避免未 lock 闪退。

0.1.43(2026-09-09)

  • 修 iOS 打开相机 / 拍照闪退:分辨率必须落在当前 format 的 supportedMaxPhotoDimensions 里,改 format 先 lockForConfiguration

0.1.42(2026-09-09)

  • 拍照后先抽样读亮度和对比度,再按纸面情况提亮、拉对比;不另加图像库,走已有 C++ 核。

0.1.41(2026-09-09)

  • 取景改为 4:3 居中,上下黑边放菜单,不再全屏铺满。
  • 顶栏下列出相机从高到低最多 5 档分辨率(默认超高清),可点选拍照尺寸。
  • 扫描时实时画矫正边框,节流约 4fps;拍照确认框与实时识别一致。

0.1.40(2026-08-20)

  • 安卓确认预览叠字按 10–28dp 计算,与 iOS 点、鸿蒙 vp 对齐,避免高分屏把 28 当成 28px。

0.1.39(2026-08-20)

  • 三端增加 stretch:false 时自动模式直接存原图,手动模式只预览打勾,不出现拉角和识别叠字。
  • 手动确认预览图时隐藏右侧「完成」,回到拍照预览再显示,避免和打勾确认误触。

0.1.38(2026-08-20)

  • 修复 iOS 拼文本 PDF 每行出现 Optional(识别文字):解析 texts 不再用模板字符串,避免 String? 被打成 Optional。

0.1.37(2026-08-20)

  • iOS 识别叠字层改为 draw(in:),不再走 draw(with:options:),避免页上出现 options 字样。

0.1.36(2026-08-20)

  • 安卓 / iOS 与鸿蒙对齐:快门先冻结预览,存档走高清拍照(约 4K);找角和 OCR 仍按长边缩小。拍照失败才回退预览帧。

0.1.35(2026-08-20)

  • 竖横混排不再只出竖排:横排框不参与纵向合并,列间距按较小框收紧。
  • 鸿蒙快门先冻结预览,再走 PhotoOutput 高清拍照做识别/拉正;拍照失败才回退预览帧。拉角预览不再压到 1600。

0.1.34(2026-08-20)

  • 竖排不再只出单字:连通域先纵向膨胀并合并同一列,瘦高框按 Paddle 那样逆时针转 90° 再送给横排 rec。

0.1.33(2026-08-20)

  • OCR 已经能出字,但 rec 输出本身就是 softmax,再做一次软最大后分数变成 0.0001,被 0.30 阈值全部丢掉,所以只提示未识别到文字。
  • 检测预处理改成 PP-OCRv5 的 [-1,1],框阈值 0.5。

0.1.32(2026-08-20)

  • 鸿蒙 OCR 识别模型按 ORT 1.16 降到 ONNX IR 8。之前 rec 是 IR 10,初始化失败只剩 ocr model not ready,校正图上也就没有文本层。
  • 三端 OCR 模型改放到 ocr2 缓存目录,避免沿用旧 rec;模型缺失或初始化失败会直接提示原因。

0.1.31(2026-08-20)

  • 拍照先冻结当前预览帧并落盘,再识别/拉正。不再等高质量拍照完成,避免处理中手一动结果变成后面的画面。

0.1.30(2026-08-20)

  • tempFiles 增加无坐标 texts 行数组,方便按页拼文本 PDF;text 仍是换行拼接的全文。

0.1.29(2026-08-20)

  • 手动确认且 ocr: true 时,拉正识别后在图上叠字;长按该行复制。框坐标按落盘图尺寸缩放,对齐 FIT_CENTER。

0.1.28(2026-08-20)

  • 复用已有 ONNX Runtime,内置 PP-OCRv5 mobile。openCorrect({ ocr: true }) 扫完出字,也可单独 ocrImage
  • 公式和表格仍按文本行识别,不做结构还原。

0.1.27(2026-08-18)

  • 鸿蒙底栏相册、完成不再被中间快门层挡住,可正常点击。

0.1.26(2026-08-18)

  • 三端去掉取景右侧白平衡、ISO 滑杆和光学防抖开关,相机恢复系统自动参数。

0.1.25(2026-08-18)

  • 鸿蒙取景右侧滑杆改为固定竖轨并避开底栏,相册和完成不再被挡住。

0.1.24(2026-08-18)

  • 白平衡 / ISO 默认 50 不再改相机自动参数,避免拍照裁切后发红。

0.1.23(2026-08-18)

  • 取景右侧改为白平衡、ISO 竖滑杆(中间为自动),底部增加光学防抖开关(默认开,实心圆/空心圆)。三端都走相机实时参数,去掉不能实时生效的降噪和对比度。

0.1.22(2026-08-18)

  • iOS 取景预览用 CIColorControls 实时套对比度,滑杆拖动即可看到效果。

0.1.21(2026-08-18)

  • iOS / 鸿蒙没有公开的相机降噪参数,取景右侧只保留对比度;降噪滑杆仅安卓显示。

0.1.20(2026-08-18)

  • 取景右侧增加降噪、对比度竖滑杆(0–100,默认 50)。安卓优先走相机硬件参数,不支持时与 iOS / 鸿蒙一样在拍照后做软件处理;相册图不改。

0.1.19(2026-08-18)

  • 主检测改为 DocAligner LCNet100 热力图(约 4.6MB),四角仍走原有贴边精修;模型不够格时回退传统算法。

0.1.18(2026-08-18)

  • 底栏取消/打勾居中后不再被左右裁切,圆钮保持 64 完整显示。

0.1.17(2026-08-18)

  • 拉角时取消和打勾作为一组在底栏左右居中,不再把取消挤到相册图标上。

0.1.16(2026-08-18)

  • 安卓缩略图拖排序对齐 iOS:浮层跟手,原位留空档,只移动空位,松手后再落图。

0.1.15(2026-08-18)

  • 手动确认拉角时,打勾左侧增加同尺寸 X 取消;取消后丢掉本张,回到预览拍照,取消按钮随即隐藏。

0.1.14(2026-08-18)

  • iOS 相机改为连续对焦。
  • 三端拍照先落盘再出「处理中」,裁切按最长边 4096 再套预览比例,避免先缩到 2048 后宽只剩 800 多。
  • 拉正输出最长边改为 4096。

0.1.13(2026-08-18)

  • 安卓全屏不再被状态栏顶下来:藏起状态栏后只按 insets 让一次顶栏/底栏。
  • 安卓相机改为连续对焦,预览走 1080p Surface,点预览可对焦。

0.1.12(2026-08-18)

  • 识别先走约 2MB 的四角小模型(DocCornerNet LEAN),分数够且通过外框校验后贴边精修;模型未加载或不可信时回退 0.1.10 传统算法。

0.1.11(2026-08-18)

  • 拍照后按预览 Cover 可视区域居中裁切,识别和拉正只处理当时看到的画面,不再带上预览外的左右杂物。

0.1.10(2026-08-18)

  • 识别在现有 C++ 上加 Hough 四边相交、分块拉对比、纸面颜色先验和真 Canny,半张纸和中间纸更稳,仍丢掉贴照片外框的候选。

0.1.9(2026-08-18)

  • 识别按扫描器主流管线重做:提对比、闭运算补纸面、轮廓+多团块打分。
  • 丢掉贴照片外框的候选,半张纸会沿边缘外扩,减少框到屏幕边和只框一半。

0.1.8(2026-08-18)

  • 识别增加局部明暗自适应,并在找到纸面后把四角贴到边缘,角更准。
  • 三端缩略图条上下加间隙,选中白边不再被裁掉一点。

0.1.7(2026-08-18)

  • 三端缩略图长按拖动改为跟手让位:拖着的块浮起,其余块立刻腾出空位,松手后顺序生效。

0.1.6(2026-08-18)

  • 识别和拉正改为 RGBA 二进制直传,不再把像素塞进 JSON/Base64。
  • 鸿蒙预览改为显示与识别同一张已校正朝向的图,四角与纸面对齐。

0.1.5(2026-08-18)

  • 纸面检测改回亮暗团块四角,收紧对边比例,减少误判成大梯形。
  • 拉正最长边改为 2048,去掉整图锐化,处理更快;鸿蒙不再整包 JSON.parse 大图,避免自动校正闪退。
  • 识别和拉正时显示「处理中...」。

0.1.4(2026-08-18)

  • 缩略图条支持长按拖动排序,完成回传按调整后的顺序。

0.1.3(2026-08-17)

  • 未识别到纸面时提示「未识别到区域,请重新拍摄」,丢弃本张并继续预览。
  • 三端拍照改为高分辨率,拉正按最长边 4096 取样并轻度锐化,减轻校正后发糊。
  • 纸面检测改为轮廓四边形 + 局部亮暗团块 + 多阈值回退,提高找纸成功率。
  • 安卓快门改为 64dp 正圆容器,避免 TextView 被底栏权重拉成椭圆。

0.1.2(2026-08-17)

  • 未识别到纸面时提示重拍并丢弃本张,不再进入拉角。
  • 拉正改为按更高分辨率原图取样,减轻校正后发糊。
  • 纸面检测改为亮暗团块四角 + 边缘回退,并放宽满幅稿面。
  • 安卓快门改为固定 64dp 正圆,避免被底栏权重拉成椭圆。

0.1.1(2026-08-17)

  • 安卓 AAR 的 classes.jar 放入占位 class,避免 HBuilderX R8 把空 jar 报成文件不存在。
  • 鸿蒙拍照预览改为沙箱 URI,避免 Image 吃绝对路径黑屏。
  • iOS 缩略图条按内容宽度排布,选中项不再被拉满整行。
  • 三端四角覆盖层提到顶栏/底栏之上,只拦截手柄触摸,标题区也能拖。

0.1.0(2026-08-17)

  • 首版:三端原生校正弹层,手动拉角与自动连拍,完成回传临时图片。
最近更新